package org.wattdepot.cli;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.client.WattDepotClientException;
import org.wattdepot.resource.property.jaxb.Property;
import org.wattdepot.resource.source.jaxb.Source;
import org.wattdepot.resource.source.summary.jaxb.SourceSummary;

/**
 * Processes and provides output for list source and list sources and list summary commands.
 * 
 * @author Dean Kim
 * 
 * 
 */
public class ListSourceCommandCli {

  /**
   * Valid sub-commands for list command.
   * 
   * @author Dean Kim
   * 
   */

  protected static final String SOURCE_COMMAND = "SOURCE";
  protected static final String SOURCES_COMMAND = "SOURCES";
  protected static final String SUMMARY_COMMAND_ERROR = "Invalid source command. Type "
      + "help for usage.";
  protected static final String SOURCES_COMMAND_ERROR = "Invalid sources command. Type "
      + "help for usage.";
  protected static final String SUMMARY_COMMAND_NET_ERROR = "There is a network error while "
      + "processing the list source command.";
  protected static final String SOURCES_COMMAND_NET_ERROR = "There is a network error while "
      + "processing the list sources command.";
  protected static final String NO_DATA_MESSAGE = "Data was not found for this entry.";
  protected static final int DEFAULT_PRINT_WIDTH = 17;

  /**
   * Pads string with designated number of spaces.
   * 
   * @param printElement String to pad.
   * @return Padded string.
   */
  protected String padSpaces(String printElement) {

    String withPad;
    StringBuffer sb = new StringBuffer();
    String pad = " ";
    int padLength = ListSourceCommandCli.DEFAULT_PRINT_WIDTH - printElement.length();

    sb.append(printElement);

    for (int i = 0; i < padLength; i++) {
      sb.append(pad);
    }

    withPad = sb.toString();

    return withPad;

  }

  /**
   * Utility method to parse and strip href string to remove URI.
   * 
   * @param href Href string.
   * @return String value minus the URI.
   */
  protected String filterHrefString(String href) {

    String[] tokenHref;
    String filteredResult;

    tokenHref = href.split("/");
    filteredResult = tokenHref[5].replaceAll("]", "");

    return filteredResult;

  }

  /**
   * Maps sources to their parents.
   * 
   * @param list List of sources.
   * @return Map contain relationship between subsources and their parents.
   */
  protected Map<String, String> mapParent(List<Source> list) {

    Map<String, String> childParent = new HashMap<String, String>();
    String[] subsourcesString;

    for (Source s : list) {
      if (!(s.getSubSources() == null)) {
        // subsources = s.getSubSources();
        subsourcesString = (s.getSubSources().toString().split(","));
        for (int i = 0; i < subsourcesString.length; i++) {
          childParent.put(this.filterHrefString(subsourcesString[i]), s.getName());
        } // end for (int i = 0; i < subsourcesString.length; i++)
      } // end if (!(s.getSubSources() == null))
    } // end for (Source s : list)

    return childParent;
  }

  /**
   * Retrieves the parent of a given source.
   * 
   * @param childParent Map containing valid source and parent pairings.
   * @param source The specified source.
   * @return Parent as a string. If no parent is found, a single space is returned.
   */
  protected String getParent(Map<String, String> childParent, String source) {

    String parent;

    if (childParent.get(source) == null) {
      parent = " ";
    }
    else {
      parent = childParent.get(source);
    }

    return parent;
  }

  /**
   * Provides the List Sources header.
   * 
   * @return List sources print header.
   */
  protected String getListSourcesPrintHeader() {

    StringBuffer sb = new StringBuffer();
    String header;

    sb.append(this.padSpaces("Name"));
    sb.append(this.padSpaces("Parent"));
    sb.append("Description\n");
    header = sb.toString();

    return header;
  }

  /**
   * Gets the row data (name, parent, description) and formats the string return.
   * 
   * @param sourceParent Map containing source parent pairings.
   * @param wattDepotSources List of sources
   * @return Formatted row data for all sources containing name, parent, and description.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String getListSourcesRows(Map<String, String> sourceParent,
      List<Source> wattDepotSources) throws WattDepotClientException {

    StringBuffer sb = new StringBuffer();
    String dataRows;
    String stringWithNewLine;

    for (Source s : wattDepotSources) {
      sb.append(this.padSpaces(s.getName()));
      sb.append(this.padSpaces(this.getParent(sourceParent, s.getName())));
      stringWithNewLine = (s.getDescription() + "\n");
      sb.append(stringWithNewLine);
    }

    dataRows = sb.toString();

    return dataRows;
  }

  /**
   * Processes the List Sources command.
   * 
   * @param client Valid WattDepot client.
   * @return Formatted output from methods handling the list sources command.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String processSources(WattDepotClient client) throws WattDepotClientException {

    StringBuffer sb = new StringBuffer();
    String returnOutput;
    List<Source> wattDepotSources;
    Map<String, String> sourceParent;

    if (client.isHealthy()) {
      wattDepotSources = client.getSources();
      sourceParent = this.mapParent(wattDepotSources);

      sb.append(this.getListSourcesPrintHeader());
      sb.append(this.getListSourcesRows(sourceParent, wattDepotSources));

      returnOutput = sb.toString();
    } // end if (client.isHealthy())
    else {
      returnOutput = ListSourceCommandCli.SOURCES_COMMAND_NET_ERROR;
    }

    return returnOutput;
  }

  /**
   * Retrieves and formats the subsources for a source.
   * 
   * @param source Valid source.
   * @return Formatted output contatining subsource information for the source.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String getSubsources(Source source) throws WattDepotClientException {

    StringBuffer sb = new StringBuffer();
    String output;
    String[] subsources;

    sb.append("SubSources: ");

    if ((source.getSubSources() == null)) {
      sb.append("No SubSources found.");
    }
    else {
      subsources = source.getSubSources().toString().split(",");
      for (int i = 0; i < subsources.length - 1; i++) {
        sb.append(this.filterHrefString(subsources[i]));
        sb.append(", ");
      } // end for (int i = 0; i < subsources.length - 1; i++)
      sb.append(this.filterHrefString(subsources[subsources.length - 1]));
      output = sb.toString() + "\n";
    } // end else

    output = sb.toString() + "\n";

    return output;

  }

  /**
   * Retrieves the description of a particular source.
   * 
   * @param source Specified source.
   * @return Formatted description output retrieved for the source.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String getSourceDescription(Source source) throws WattDepotClientException {

    StringBuffer sb = new StringBuffer();
    String output;

    sb.append("Description: ");
    sb.append(source.getDescription());

    output = sb.toString() + "\n";

    return output;

  }

  /**
   * Retrieves the source owner.
   * 
   * @param source Specified source.
   * @return Formatted owner output retrieved for the source.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String getSourceOwner(Source source) throws WattDepotClientException {
    StringBuffer sb = new StringBuffer();
    String[] tokens;
    String owner;
    String output;

    sb.append("Owner: ");
    tokens = source.getOwner().split("/");
    owner = tokens[(tokens.length - 1)];
    sb.append(owner);

    output = sb.toString() + "\n";

    return output;
  }

  /**
   * Retrieves the source location.
   * 
   * @param source Specified source.
   * @return Formatted location output retrieved for the source.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String getSourceLocation(Source source) throws WattDepotClientException {
    StringBuffer sb = new StringBuffer();
    String location;
    String output;

    sb.append("Location: ");
    location = source.getLocation();
    sb.append(location);

    output = sb.toString() + "\n";

    return output;
  }

  /**
   * Retrieves the source coordinates.
   * 
   * @param source Specified source.
   * @return Formatted coordinates output retrieved for the source.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String getSourceCoordinates(Source source) throws WattDepotClientException {
    StringBuffer sb = new StringBuffer();
    String coordinates;
    String output;

    sb.append("Coordinates: ");
    coordinates = source.getCoordinates();
    sb.append(coordinates);

    output = sb.toString() + "\n";

    return output;
  }

  /**
   * Retrieves the source properties.
   * 
   * @param source Specified source.
   * @return Formatted properties output retrieved for the source if properties are set.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String getSourceProperties(Source source) throws WattDepotClientException {
    StringBuffer sb = new StringBuffer(50);
    String output;
    List<Property> propertyList;

    sb.append("Properties: ");

    if (source.isSetProperties()) {
      propertyList = source.getProperties().getProperty();
      for (Property p : propertyList) {
        sb.append('(');
        sb.append(p.getKey());
        sb.append(" : ");
        sb.append(p.getValue());
        sb.append("), ");
      }
      sb.delete(sb.length() - 2, sb.length() - 1);
    }
    else {
      sb.append("No properties defined.");

    }
    output = sb.toString() + "\n";

    return output;
  }

  /**
   * Retrieves the earliest timestamp for a given source.
   * 
   * @param client Valid WattDepotClient.
   * @param source Specified source.
   * @return Formatted earliest timestamp output retrieved for the source.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String getSourceEarliestDataTimestamp(WattDepotClient client, Source source)
      throws WattDepotClientException {
    StringBuffer sb = new StringBuffer();
    SourceSummary summary;
    String output;

    summary = client.getSourceSummary(source.getName());
    sb.append("Earliest data: ");
    sb.append(summary.getFirstSensorData().toString());
    output = sb.toString() + "\n";

    return output;
  }

  /**
   * Retrieves the latest timestamp for a given source.
   * 
   * @param client Valid WattDepotClient.
   * @param source Specified source.
   * @return Formatted latest timestamp output retrieved for the source.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String getSourceLatestDataTimestamp(WattDepotClient client, Source source)
      throws WattDepotClientException {

    StringBuffer sb = new StringBuffer();
    SourceSummary summary;
    String output;

    summary = client.getSourceSummary(source.getName());
    sb.append("Latest data: ");
    sb.append(summary.getLastSensorData().toString());
    output = sb.toString() + "\n";

    return output;
  }

  /**
   * Retrieves the number of data elements for a given source.
   * 
   * @param client Valid WattDepotClient.
   * @param source Specified source.
   * @return Formatted output indicating number of data elements retrieved for the source.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String getSourceDataCountTotal(WattDepotClient client, Source source)
      throws WattDepotClientException {

    StringBuffer sb = new StringBuffer(25);
    SourceSummary summary;
    String output;

    summary = client.getSourceSummary(source.getName());
    sb.append("Total data points: ");
    sb.append(Long.toString(summary.getTotalSensorDatas()));
    output = sb.toString() + "\n";

    return output;
  }

  /**
   * Processes the list source command and returns the formatted output.
   * 
   * @param client Verified Watt Depot Client.
   * @param stringSource Source to query.
   * @return Formatted output containing data for soruce.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String processSummary(WattDepotClient client, String stringSource)
      throws WattDepotClientException {

    StringBuffer sb = new StringBuffer();
    String returnOutput = null;
    Source source;

    if (client.isHealthy()) {
      try {
        client.getSource(stringSource);
        source = client.getSource(stringSource);
        sb.append(this.getSubsources(source));
        sb.append(this.getSourceDescription(source));
        sb.append(this.getSourceOwner(source));
        sb.append(this.getSourceLocation(source));
        sb.append(this.getSourceCoordinates(source));
        sb.append(this.getSourceProperties(source));
        sb.append(this.getSourceEarliestDataTimestamp(client, source));
        sb.append(this.getSourceLatestDataTimestamp(client, source));
        sb.append(this.getSourceDataCountTotal(client, source));
        returnOutput = sb.toString();
      }
      catch (ResourceNotFoundException e) {
        returnOutput = "Invalid source.\n";
      }

    } // end if (client.isHealthy())
    else {
      returnOutput = ListSourceCommandCli.SUMMARY_COMMAND_NET_ERROR;
    }

    return returnOutput;
  }

  /**
   * Entry and return point for processing list source and list sources commands.
   * 
   * @param client Valid WattDepot client.
   * @param input Tokenized command.
   * @return Formatted output for the respective commands.
   * @throws WattDepotClientException Throws WattDepotClientException.
   */
  protected String processCommand(WattDepotClient client, String[] input)
      throws WattDepotClientException {

    String commandOutput = "Default list source/sources command";
    if (input[0].equals(ListSourceCommandCli.SOURCES_COMMAND)) {
      if (input.length == 1) {
        commandOutput = this.processSources(client);
      }
      else {
        commandOutput = ListSourceCommandCli.SOURCES_COMMAND_ERROR;
      }

    } // end if (input[1].equals(ListSourceCommandCli.SOURCES_COMMAND))
    if (input[0].equals(ListSourceCommandCli.SOURCE_COMMAND)) {
      if ((input.length == 3) && (input[2].equals("SUMMARY"))) {
        commandOutput = this.processSummary(client, input[1]);
      } // end if (input[1].equals(ListSourceCommandCli.SOURCE_COMMAND))
      else {
        commandOutput = ListSourceCommandCli.SUMMARY_COMMAND_ERROR;
      } // end else
    } // end if (input[1].equals(ListSourceCommandCli.SOURCE_COMMAND)

    return commandOutput;
  }

}
